Fix off-by-one that corrupts a bitmap in remove_smallest/remove_biggest (#359) - #363
Merged
Kerollmops merged 1 commit intoAug 12, 2026
Conversation
When the amount to remove exactly equals the run length of the interval
where removal stops, IntervalStore::remove_smallest shrank that interval
to start = end + 1 and remove_biggest shrank it to end = start - 1,
producing an inverted interval. run_len() then underflows on end - start
(a debug-build panic, silent corruption in release): e.g.
{0,1,2,4}.remove_smallest(3) returned a bitmap of length 65537 instead
of {4}.
Drop the exactly-consumed interval instead of shrinking it: use <= (not
<) in remove_smallest and > (not >=) in remove_biggest so the boundary
interval is removed by the drain. Add boundary tests for both.
Fixes RoaringBitmap#359
Kerollmops
approved these changes
Aug 12, 2026
Kerollmops
left a comment
Member
There was a problem hiding this comment.
Hey @youdie006 👋
Thank you very much to you and @DRMacIver for the report and the bug fix. I'll release this fix as part of the next version release.
Have a nice day 🌵
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #359.
Problem
For a run/interval container,
IntervalStore::remove_smallest(n)andremove_biggest(n)have an off-by-one at the interval where removal stops whennexactly equals that interval's run length:remove_smallestdoesstart += amount, movingstarttoend + 1.remove_biggestdoesend -= amount, movingendtostart - 1.Either way the interval becomes inverted (
start > end), andrun_len()(end - start + 1) then underflows on the subtraction: a debug-build panic ("attempt to subtract with overflow"), or silent corruption in release. The reporter's example:{0,1,2,4}.remove_smallest(3)should yield{4}but returns a bitmap of length 65537.Fix
Drop the exactly-consumed interval instead of shrinking it:
remove_smallest:if last_interval.run_len() < amountbecomes<=, so an interval whose whole run is removed is dropped viaremove_to += 1rather than shrunk.remove_biggest:if last_interval.run_len() >= amountbecomes>, so the exactly-consumed interval falls through to thedrain(remove_to..)that removes it, instead of shrinking itsend.Test
Added
remove_smallest_exact_interval_boundaryandremove_biggest_exact_interval_boundary(the reporter's two cases at theIntervalStorelevel). Red-green verified withcargo test -p roaring: before the fix both assert against inverted intervals ([3, 2]/[4, 3]); after, they drop the whole interval. The fullroaringtest suite (including the property tests) passes;rustfmt --checkandclippyare clean.